﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Combinatorics.Collections;
using RuleOfReady.Core.DomainModels.Tiles;


namespace RuleOfReady.Core.DomainModels.Sets
{
    /// <summary>
    /// Object for storing a Mahjong Sequence or Run (Chinese: 'Chow', Japanese: 'Shuntsu')
    /// A sequence is 3 tiles of the same Suit that are sequential (2 3 4 of dots, for example)
    /// 8 9 1 and 9 1 2 are NOT sequences, unlike (certain forms of) Gin Rummy
    /// </summary>
    public class MahjongSequence : MahjongPossibleSet
    {

        #region MahjongPossibleSet Properties

        /// <summary>
        /// A list of tiles that form the sequence
        /// </summary>
        public override IEnumerable<MahjongTile> Tiles { get; protected set; }

        /// <summary>
        /// Tiles still needed to complete the set: there are none, since a sequence is a complete set
        /// </summary>
        public override IEnumerable<MahjongTile> WaitingTiles { get; protected set; }

        /// <summary>
        /// Flag: A sequence is a completed set
        /// </summary>
        public override bool CompletedSet { get; protected set; }

        #endregion

        /// <summary>
        /// a list of partial sequences (2 tiles of the three that form the sequence)
        /// </summary>
        public IEnumerable<MahjongPartialSequence> PartialSequences { get; private set; }

        /// <summary>
        /// Create a new Mahjong tile sequence object, given a list of tiles that form a sequence
        /// </summary>
        /// <param name="sequenceTiles">a sequence of tiles, (for example 2, 3, and 4 of dots)</param>
        public MahjongSequence(IEnumerable<MahjongSuitTile> sequenceTiles)
        {
            if (sequenceTiles.Count() != 3)
                throw new ArgumentException("There must be exactly 3 tiles in a sequence");
            if (!this.TilesAreASequence(sequenceTiles))
                throw new ArgumentException("The given tiles do not form a sequence");

            this.Tiles = sequenceTiles.Cast<MahjongTile>();

            IEnumerable<IEnumerable<MahjongSuitTile>> partialSequences =
                new Combinations<MahjongSuitTile>(sequenceTiles.ToList(), 2, GenerateOption.WithoutRepetition);

            this.PartialSequences = partialSequences.Select(partialSequence => new MahjongPartialSequence(partialSequence));
            this.WaitingTiles = new List<MahjongTile>();
            this.CompletedSet = true;
        }

        /// <summary>
        /// Gives a list of partial sequences that are in the given list of tiles
        /// </summary>
        /// <param name="givenTiles">a list of tiles to check</param>
        /// <returns></returns>
        public IEnumerable<IEnumerable<MahjongTile>> FindPartialSequences(IEnumerable<MahjongTile> givenTiles)
        {
            return PartialSequences.Where(partialSeq => new HashSet<MahjongTile>(partialSeq).IsSubsetOf(givenTiles));
        }

        /// <summary>
        /// (MahjongPossibleSet) Add a tile to the sequence to make a set 
        /// </summary>
        /// <remarks>
        /// No tiles can be added to a sequence to make a set
        /// </remarks>
        /// <param name="addTile">tile to all</param>
        /// <returns>null (no possible sets)</returns>
        public override MahjongPossibleSet Add(MahjongTile addTile)
        {
            return null;
        }

        /// <summary>
        /// Predicate function: do the given suit tiles form a sequence?
        /// </summary>
        /// <param name="sequenceTiles"></param>
        /// <returns></returns>
        private bool TilesAreASequence(IEnumerable<MahjongSuitTile> sequenceTiles)
        {
            IList<MahjongSuitTile> orderedSeqTiles = sequenceTiles.OrderBy(tile => tile).ToList();
            MahjongSuitType sequenceSuitType = orderedSeqTiles[0].SuitType;

            //all tiles must be the same suit
            if (sequenceTiles.Where(tile => tile.SuitType != sequenceSuitType).Any())
                return false;

            //tiles must have sequential numbers (2 3 4, for example)
            if ((orderedSeqTiles[0].SuitNumberInt + 1 == orderedSeqTiles[1].SuitNumberInt) &&
                (orderedSeqTiles[1].SuitNumberInt + 1 == orderedSeqTiles[2].SuitNumberInt))
                return true;
            else
                return false;
        }

    }
}
